﻿using System;
using System.Collections.Generic;

namespace WebCrawler
{
    public class HTMLDocGraph : IHTMLDocGraph
    {
        private class HTMLNode
        {
            private IHTMLDoc doc;
            private IList<HTMLNode> children;

            public HTMLNode(IHTMLDoc doc)
            {
                this.doc = doc;
                children = new List<HTMLNode>();
            }

            public HTMLNode()
            {
                this.doc = null;
                children = new List<HTMLNode>();
            }

            public IHTMLDoc Doc
            {
                get { return this.doc; }
                set { this.doc = value; }
            }

            public void addChild(HTMLNode node)
            {
                children.Add(node);
            }

            public IList<HTMLNode> getChildren()
            {
                return children;
            }
        }

        private IList<HTMLNode> nodeList; /* !!! Change to hash map */
        private HTMLNode rootNode;
        

        public HTMLDocGraph(string url)
        {
            nodeList = new List<HTMLNode>();

            /* Create root node and add first child */
            rootNode = new HTMLNode();
            HTMLNode childNode = new HTMLNode(new HtmlDocRegEx(url));
            rootNode.addChild(childNode);
            nodeList.Add(childNode);
        }

        /// <summary>
        /// Do breath-first and find first unvisited HTMLDoc in the Graph
        /// </summary>
        /// <returns></returns>
        public IHTMLDoc GetNextUnvisitedDoc()
        {
            List<HTMLNode> checkedList = new List<HTMLNode>();
            Queue<HTMLNode> queue = new Queue<HTMLNode>();
            queue.Enqueue(rootNode.getChildren()[0]);

            while (true)
            {
                /* Get element from the queue , return if it is unvisited,
                 * if visited add its children to queue */
                if (queue.Count == 0)
                    return null;
                HTMLNode node = queue.Dequeue();
                if (node.Doc.Visited == false)
                    return node.Doc;

                foreach (HTMLNode childNode in node.getChildren())
                {
                    if (!checkedList.Contains(childNode))
                    {
                        queue.Enqueue(childNode);
                        checkedList.Add(childNode);
                    }
                }
            }
        }

        /// <summary>
        /// Return the first IHTMLDoc
        /// </summary>
        /// <returns></returns>
        public IHTMLDoc getRootDoc()
        {
            /* The first IHTML doc is root nodes child */
            return rootNode.getChildren()[0].Doc;
        }

        public IList<IHTMLDoc> GetChildren(IHTMLDoc doc)
        {
            List<IHTMLDoc> docList = new List<IHTMLDoc>();
            foreach(HTMLNode node in nodeList)
            {
                if (node.Doc == doc)
                {
                    foreach (HTMLNode child in node.getChildren())
                    {
                        docList.Add(child.Doc);
                    }
                }
            }

            return docList;
        }

        public bool IsEmpty()
        {
            return (nodeList.Count == 0);
        }

        public void Add(IHTMLDoc doc)
        {
            HTMLNode node = findNode(doc.URL);
            /* Update nodes HTML document */
            node.Doc = doc;
            /* Create children */
            createChildNodes(node);
        }

        private void createChildNodes(HTMLNode node)
        {
            foreach (string link in node.Doc.GetAllLinks())
            {
                /* Check if we already have this link */
                HTMLNode childNode = findNode(link);
                if (childNode == null)
                {
                    childNode = new HTMLNode(new HtmlDocRegEx(link));
                    nodeList.Add(childNode);
                }

                node.addChild(childNode);
            }
        }

        private HTMLNode findNode(string url)
        {
            /* Find the node */
            foreach (HTMLNode node in nodeList)
            {
                if (string.Equals(node.Doc.URL, url, StringComparison.CurrentCultureIgnoreCase))
                {
                    return node;
                }
            }

            return null;
        }
    }
}